#pragma once

#include <string_view>
#include <stdexcept>
#include <cassert>
#include <variant>
#include <vector>
#include <unordered_map>
#include <memory>
#include <utility>
#include <charconv>

namespace tinyUtil {
    class NonCopyAble {
    public:
        NonCopyAble(const NonCopyAble&) = delete;
        NonCopyAble& operator=(const NonCopyAble&) = delete;
    protected:
        NonCopyAble() = default;
        ~NonCopyAble() = default;
    };
    /*  char | type
     * -------------
     *     n | null
     *     t | true
     *     f | false
     *     " | string
     * - 0-9 | number
     *     [ | array
     *     { | object
     */
    enum class JsonValueTypeEnum {
        TYPE_NULL, TYPE_BOOL, TYPE_NUMBER, TYPE_STRING, TYPE_ARRAY, TYPE_OBJECT,
    };
    bool isDigit(char ch);
    bool isWhiteSpace(char ch);
    JsonValueTypeEnum getJsonTypeEnumByChar(char ch);
    std::string jsonValueTypeStr(JsonValueTypeEnum jsonType);

    class JsonArray;
    class JsonObject;
    using JsonValueType = std::variant<decltype(nullptr), bool, std::string, double,
                                        std::shared_ptr<JsonArray>, std::shared_ptr<JsonObject>>;

    class JsonValueNode {
        friend class JsonObjectFactory;
    private:
        JsonValueType value_;
        JsonValueTypeEnum type_;
        void check(JsonValueTypeEnum type) const;
        JsonValueNode(JsonValueTypeEnum type, JsonValueType value)
            : type_{ type }, value_{ std::move(value) } {}
    public:
        JsonValueNode(const JsonValueNode&) = delete;
        JsonValueNode& operator=(const JsonValueNode&) = delete;
        JsonValueNode(JsonValueNode&&) noexcept = default;
        JsonValueNode& operator=(JsonValueNode&&) noexcept = default;
        ~JsonValueNode() = default;

        bool isNull() const noexcept {
            return type_ == JsonValueTypeEnum::TYPE_NULL;
        }

        const JsonValueType& getValue() const noexcept {
            return value_;
        }

        bool getValueAsBool() const {
            check(JsonValueTypeEnum::TYPE_BOOL);
            return std::get<bool>(value_);
        }

        const std::string& getValueAsStr() const {
            check(JsonValueTypeEnum::TYPE_STRING);
            return std::get<std::string>(value_);
        }

        double getValueAsNum() const {
            check(JsonValueTypeEnum::TYPE_NUMBER);
            return std::get<double>(value_);
        }

        std::shared_ptr<JsonArray> getValueAsArray() const {
            check(JsonValueTypeEnum::TYPE_ARRAY);
            return std::get<std::shared_ptr<JsonArray>>(value_);
        }

        std::shared_ptr<JsonObject> getValueAsObject() const {
            check(JsonValueTypeEnum::TYPE_OBJECT);
            return std::get<std::shared_ptr<JsonObject>>(value_);
        }
    };

    class JsonArray : NonCopyAble {
        friend class JsonObjectFactory;
    private:
        std::vector<JsonValueNode> values_;
        JsonArray() = default;
    public:
        JsonValueNode& getNode(int index) {
            return values_.at(index);
        }

        const JsonValueNode& getNode(size_t index) const {
            return values_.at(index);
        }

        const JsonValueNode& operator[](size_t index) const {
            return getNode(index);
        }

        decltype(auto) getBoolValue(size_t index) const {
            return getNode(index).getValueAsBool();
        }

        decltype(auto) getNumValue(size_t index) const {
            return getNode(index).getValueAsNum();
        }

        decltype(auto) getStrValue(size_t index) const {
            return getNode(index).getValueAsStr();
        }

        const JsonArray& getArrayValue(size_t index) const {
            return *getNode(index).getValueAsArray();
        }

        const JsonObject& getObjectValue(size_t index) const {
            return *getNode(index).getValueAsObject();
        }
    };

    class JsonObject : NonCopyAble {
        friend class JsonObjectFactory;

    public:
        std::unordered_map<std::string_view, JsonValueNode> nodes;
        JsonObject() = default;
    public:
        const JsonValueNode& getNode(std::string_view key) const {
            if (nodes.find(key) == nodes.end()) throw std::runtime_error{ "no such key in json" };
            return nodes.at(key);
        }

        decltype(auto) getBoolValue(std::string_view key) const {
            return getNode(key).getValueAsBool();
        }

        decltype(auto) getNumValue(std::string_view key) const {
            return getNode(key).getValueAsNum();
        }

        decltype(auto) getStrValue(std::string_view key) const {
            return getNode(key).getValueAsStr();
        }

        const JsonArray& getArrayValue(std::string_view key) const {
            return *getNode(key).getValueAsArray();
        }

        const JsonObject& getObjectValue(std::string_view key) const {
            return *getNode(key).getValueAsObject();
        }
    };

    class JsonObjectFactory : NonCopyAble {
    private:
        std::string_view jsonStr_;
        size_t pos_{ 0 };
        std::shared_ptr<JsonObject> jsonObject_{ nullptr };
        void skipWhiteSpace();
        std::string_view generateKey();
        JsonValueType generateValue(JsonValueTypeEnum type);
        bool generateBoolValue();
        decltype(nullptr) generateNullValue();
        double generateNumValue();
        std::string generateStrValue();
        std::shared_ptr<JsonArray> generateArrayValue();
        std::shared_ptr<JsonObject> generateObjValue();
        JsonValueNode generateValueNode();
    public:
        explicit JsonObjectFactory(std::string_view jsonStr) noexcept : jsonStr_{ jsonStr } {}
        std::shared_ptr<JsonObject> createJsonObj() {
            if (!jsonObject_) jsonObject_ = generateObjValue();
            return jsonObject_;
        }
    };
}